export function readImg(url: string): Promise<{ img: HTMLImageElement; w: number; h: number }> {
    return new Promise((r) => {
        const img = new Image();
        img.src = url;
        img.onload = function () {
            r({ img, w: img.width, h: img.height });
        };
    });
}
export function readVideo(url: string): Promise<{ video: HTMLVideoElement; w: number; h: number }> {
    return new Promise((r) => {
        const video = document.createElement('video');
        video.onloadedmetadata = () => {
            r({ video, w: video.videoWidth, h: video.videoHeight });
        };
        video.src = url;
        video.load();
    });
}

export function importJs(src, key) {
    const hash = btoa(key || src);
    if (document.querySelector(`[data-hash='${hash}']`)) return Promise.resolve();
    return new Promise((r, j) => {
        const el = document.createElement('script');
        el.src = src;
        el.dataset.hash = hash;

        el.onload = r;
        el.onerror = j;

        document.head.appendChild(el);
    });
}

export function importCss(src, key) {
    const hash = btoa(key || src);
    if (document.querySelector(`[data-hash='${hash}']`)) return Promise.resolve();
    return new Promise((r, j) => {
        const el = document.createElement('link');
        el.rel = 'stylesheet';
        el.href = src;
        el.dataset.hash = hash;

        el.onload = r;
        el.onerror = j;

        document.head.appendChild(el);
    });
}

interface VideoFrameOptions {
    onFrame: (img: string) => void;
    fps: number;
}

export class VideoFrame {
    frameInterval: number;
    canvas: HTMLCanvasElement;
    opts: VideoFrameOptions;
    video: HTMLVideoElement = document.createElement('video');
    ctx: CanvasRenderingContext2D;
    play: any;
    reqId: number = -1;
    captureTime: number = Date.now();
    constructor(
        public url: string,
        opts: Partial<VideoFrameOptions>,
    ) {
        this.opts = { fps: 30, onFrame: () => {}, ...opts };
        this.frameInterval = 1000 / this.opts.fps;
        this.canvas = document.createElement('canvas');
        this.ctx = this.canvas.getContext('2d')!;
    }
    initVideo(url) {
        const next_url = url || this.url;

        return new Promise((r, j) => {
            if (!next_url) return j(new Error('url is required'));

            const video = document.createElement('video');
            this.video = video;

            video.src = next_url;
            video.muted = true; // 解决自动播放限制
            video.preload = 'metadata';

            // 元数据加载完成后初始化
            video.addEventListener('loadedmetadata', () => {
                this.canvas.width = video.videoWidth;
                this.canvas.height = video.videoHeight;
                this.ctx = this.canvas.getContext('2d')!;
                r(this.video);

                video.addEventListener('seeked', (e) => {
                    this.opts.onFrame?.(this.capture());
                });
            });

            // 错误处理
            video.addEventListener('error', (e) => {
                j(e);
            });
        });
    }

    seek(_time) {
        const time = Math.max(0, Math.min(_time, this.video.duration)) * 1e3;
        return new Promise((r, j) => {
            if (time > this.video.duration) return j(new Error('time is out of range'));
            const call = () => {
                this.video.removeEventListener('seeked', call);
                r(this.capture());
            };
            this.video.addEventListener('seeked', call);
            this.video.currentTime = time;
        });
    }
    start() {
        if (this.play) return;
        this.play = true;
        this.video.play();
        this.reqId = requestAnimationFrame(this.loopCapture.bind(this));
    }
    pause() {
        if (!this.play) return;
        this.play = false;
        this.video.pause();
        cancelAnimationFrame(this.reqId);
    }
    toggle() {
        if (this.play) {
            this.pause();
        } else {
            this.start();
        }
    }

    loopCapture() {
        if (!this.captureTime || Date.now() - this.captureTime >= this.frameInterval) {
            this.opts.onFrame?.(this.capture());
        }
        this.reqId = requestAnimationFrame(this.loopCapture.bind(this));
    }

    capture() {
        this.ctx.clearRect(0, 0, this.canvas.width, this.canvas.height);
        this.ctx.drawImage(this.video, 0, 0, this.canvas.width, this.canvas.height);
        this.captureTime = Date.now();
        return this.canvas.toDataURL('image/jpeg', 0.8);
    }
}
